/*******************************************************************************
 * Copyright (c) 2008-2009 zhang yuexiang.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 *      http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */ 
package org.xfeep.alaya.jta;

import javax.ejb.EJBException;
import javax.ejb.TransactionAttributeType;
import javax.ejb.TransactionRolledbackLocalException;
import javax.transaction.NotSupportedException;
import javax.transaction.RollbackException;
import javax.transaction.SystemException;
import javax.transaction.Transaction;
import javax.transaction.TransactionManager;
import static javax.transaction.Status.*;

/**
 * Transaction local interceptor in a business method whose transaction type is container-managed.
 * @author zhang yuexiang
 *
 */
public class TransactionLocalInterceptor {

	TransactionManager tm;
	boolean startedTransaction = false;
	TransactionAttributeType type = TransactionAttributeType.REQUIRED;
	Transaction current = null; // if REQUIRES_NEW, we should use it to resume the current transaction
	
	public TransactionLocalInterceptor(TransactionManager txm) {
		this(txm, TransactionAttributeType.REQUIRED);
	}
	
	

	public TransactionLocalInterceptor(TransactionManager txm, String txType) {
		this(txm, TransactionAttributeType.valueOf(txType));
	}
	
	public TransactionLocalInterceptor(TransactionManager txm, TransactionAttributeType txType) {
		super();
		this.tm = txm;
		this.type = txType;
		start();
	}
	
	public void start(){
		switch(type){
			case MANDATORY:{
				if (!activatedTransaction(tm)) {
                    throw new EJBException("No active Transaction within current method whose transaction type is MANDATORY");
                }
				break;
			}
			case NEVER:{
				if (activatedTransaction(tm)) {
                    throw new EJBException("Meet active Transaction within current method whose transaction type is NEVER");
                }
				break;
			}
			case NOT_SUPPORTED:{
				if (activatedTransaction(tm)){
					try {
						current = tm.suspend();
					} catch (SystemException e) {
						throw new EJBException("Current transaction can  not supend.", e);
					}
				}
				break;
			}
			case REQUIRED:{
				if (!activatedTransaction(tm)){
					try {
						tm.begin();
					} catch (NotSupportedException e) {
		                throw new EJBException("Transaction Manager implementation does not support nested transactions.", e);
		            } catch (SystemException e) {
		                throw new EJBException("Cannot call begin() on the transaction manager.", e);
		            }
					startedTransaction = true;
				}
				break;
			}
			case REQUIRES_NEW:{
				if (activatedTransaction(tm)){
					try {
						current = tm.suspend();
					} catch (SystemException e) {
						throw new EJBException("Current transaction can  not supend.", e);
					}
				}
				try {
					tm.begin();
				} catch (NotSupportedException e) {
	                throw new EJBException("Transaction Manager implementation does not support nested transactions.", e);
	            } catch (SystemException e) {
	                throw new EJBException("Cannot call begin() on the transaction manager.", e);
	            }
				startedTransaction = true;
				break;
			}
			case SUPPORTS:{
				break;
			}
		}
	}
	
	public void catchException(Throwable e){
		if (activatedTransaction(tm)){
			try {
				tm.setRollbackOnly();
			} catch (Exception e1) {
				throw new EJBException("Cannot call setRollbackOnly() on the transaction manager.", e1);
			} 
		}
	}
	
	public void complete(){
		
		
		switch(type){
			case MANDATORY:{
				break;
			}
			case NEVER:{
				break;
			}
			case NOT_SUPPORTED:{
				if (current != null){
					try {
						tm.resume(current);
					} catch (Exception e) {
						throw new EJBException("can not resume suspend transaction", e);
					}
				}
				break;
			}
			case REQUIRED:{
				if (startedTransaction){
					// just a check.
                    Transaction transactionAfter = null;
                    try {
                        transactionAfter = tm.getTransaction();
                    } catch (SystemException se) {
                        throw new EJBException("Cannot get the current transaction on transaction manager.", se);
                    }

                    if (transactionAfter == null) {
                        throw new RuntimeException("Transaction disappeared.");
                    }

                    try {
                    	TransactionLifeCycleMonitorService.notifyBeforeEvent(transactionAfter);
                        switch (tm.getStatus()) {
                        case STATUS_ACTIVE:
                            tm.commit();
                            break;
                        case STATUS_MARKED_ROLLBACK:
                            tm.rollback();
                            break;
                        default:
                            throw new RuntimeException("Unexpected transaction status" + tm.getStatus());
                        }
                    } catch (RollbackException e) {
                        throw new TransactionRolledbackLocalException("Could not commit transaction", e);
                    } catch (Exception e) {
                        throw new EJBException("Container exception:" + e.getMessage(), e);
                    }finally{
                    	try {
							TransactionLifeCycleMonitorService.notifyAfterEvent(transactionAfter, tm.getStatus());
						} catch (SystemException e) {
							// TODO Auto-generated catch block
							e.printStackTrace();
						}
                    }
				}
				break;
			}
			case REQUIRES_NEW:{
				try{
					if (startedTransaction){
	                    Transaction transactionAfter = null;
	                    try {
	                        transactionAfter = tm.getTransaction();
	                    } catch (SystemException se) {
	                        throw new EJBException("Cannot get the current transaction on transaction manager.", se);
	                    }

	                    if (transactionAfter == null) {
	                        throw new RuntimeException("Transaction disappeared.");
	                    }

	                    try {
	                    	TransactionLifeCycleMonitorService.notifyBeforeEvent(transactionAfter);
	                        switch (tm.getStatus()) {
	                        case STATUS_ACTIVE:
	                            tm.commit();
	                            break;
	                        case STATUS_MARKED_ROLLBACK:
	                            tm.rollback();
	                            break;
	                        default:
	                            throw new RuntimeException("Unexpected transaction status" + tm.getStatus());
	                        }
	                    } catch (RollbackException e) {
	                        throw new TransactionRolledbackLocalException("Could not commit transaction", e);
	                    } catch (Exception e) {
	                        throw new EJBException("Container exception:" + e.getMessage(), e);
	                    }finally{
	                    	try {
								TransactionLifeCycleMonitorService.notifyAfterEvent(transactionAfter, tm.getStatus());
							} catch (SystemException e) {
								// TODO Auto-generated catch block
								e.printStackTrace();
							}
	                    }
					}
				}finally{
					if (current != null){
						try {
							tm.resume(current);
						} catch (Exception e) {
							throw new EJBException("can not resume suspend transaction", e);
						}
					}
				}
				
				break;
			}
			case SUPPORTS:{
				break;
			}
		}
	}


    protected   boolean activatedTransaction(final TransactionManager tm)  {
        try {
			return tm.getStatus() != STATUS_NO_TRANSACTION;
		} catch (SystemException e) {
			throw new EJBException(e);
		}
    }


    protected   boolean isRollbackOnly(final TransactionManager tm)  {
        try {
			return tm.getStatus() == STATUS_MARKED_ROLLBACK;
		} catch (SystemException e) {
			throw new EJBException(e);
		}
    }
	
	public  static RuntimeException wrapRuntimeException(Throwable e) {
		if (e instanceof RuntimeException){
			return (RuntimeException)e;
		}
		return  new RuntimeException(e);
	}

	
	
}
